#!/usr/bin/env jython
# -*- coding: utf-8 -*-
#
#	   sans titre.py
#	   
#	   Copyright 2011 Eric <eric@ericaro.net>
#	   
#	   This program is free software; you can redistribute it and/or modify
#	   it under the terms of the GNU General Public License as published by
#	   the Free Software Foundation; either version 2 of the License, or
#	   (at your option) any later version.
#	   
#	   This program is distributed in the hope that it will be useful,
#	   but WITHOUT ANY WARRANTY; without even the implied warranty of
#	   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	   GNU General Public License for more details.
#	   
#	   You should have received a copy of the GNU General Public License
#	   along with this program; if not, write to the Free Software
#	   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#	   MA 02110-1301, USA.
import os,sys
from string import Template

# this is a dirty code, but it's intended to only live for a short time while bootstrapping neojavagen

projectname = "net.ericaro.neojavagen"

class LangType(list):
	"extend a list to support production rules, and package and more to come"
	
	def __init__(self , name, objects):
		super(LangType, self).__init__()
		self.package=""
		self.name = name
		self.interfaces=[]
		self.imports = []
		self.objects= objects
		
	
	def __str__(self):
		res = self.package+"."+self.name+ " interfaces "+str(self.interfaces)+"\n"
		for prod in self:
			res+="\t"+prod+"\n"
		return res
	
	def docRule(self):
		"generate the javadoc rule"
		name= self.name
		rules = "\n*     ".join(self)
		rules= rules.replace("*/", "*&#47;")
		return Template("*<pre> $name :\n    $rules\n</pre>").substitute(locals())
	
	def importStatement(self):
		"return a list of unique qualified name imports"
		return ";\n".join("import "+self.qnameOf(i) for i in set(self.imports) )
		
	
	def qname(self):
		"the fully qualified name"
		return self.package+"."+self.name
	def qnameOf(self, refType):
		try:
			return self.objects[refType].qname() 
		except:
			return projectname+"."+refType


def nameSymbol(symbol):
	"convert a symbol to a name"
	reserved = [   "abstract",	"continue",	"for",		   "new",		  "switch",
		"assert",	  "default",	 "if",			"package",	  "synchronized",
		"boolean",	 "do",		  "goto",		  "private",	  "this",
		"break",	   "double",	  "implements",	"protected",	"throw",
		"byte",		"else",		"import",		"public",	   "throws",
		"case",		"enum",		"instanceof",	"return",	   "transient",
		"catch",	   "extends",	 "int",		   "short",		"try",
		"char",		"final",	   "interface",	 "static",	   "void", 
		"class",	   "finally",	 "long",		  "strictfp",	 "volatile",
		"const",	   "float",	   "native",		"super",		"while]",]
		
	if symbol in reserved:
		return "a"+symbol
		
	map ={ 
	','    : 'comma'          ,
	'...'  : 'tripleDot'      ,
	'='    : 'equalsSymbol'   ,
    '*='   : 'starEquals'     ,
    '/='   : 'slashEquals'    ,
    '%='   : 'percentEquals'  ,
    '+='   : 'plusEquals'     ,
    '-='   : 'minusEquals'    ,
    '<<='  : 'llEquals'       ,
    '>>='  : 'ssEquals'       ,
    '>>>=' : 'sssEquals'      ,
    '&='   : 'andEquals'      ,
    '^='   : 'xorEquals'      ,
    '|='   : 'orEquals'       ,
	}
	
	if symbol in map.keys():
		return map[symbol]
	
	else:
		return symbol
	
	
	


class OperationRule(object):
	"versatile class that handles composite rules elements"
	def __init__(self, rule):
		self.literal = (rule.lower()==rule )
		self.optional = rule.endswith("?") and len(rule)>1
		
		# find the field name
		if self.optional :
			rule = rule[:-1] # remove the trailing ?
		if self.literal:
			self.javaType="boolean"
			self.value = '"'+rule+'"'
			if rule in {'{', ';' , '}', '*/'}:
				self.value= '"'+rule+r'\n"'
			if self.optional:
				self.field = nameSymbol(rule) # need to set the field too
			else:
				self.field = None # useless
		else:
			self.value= self.field = nameSymbol(rule.lower())
			self.javaType = rule
	
	
	def operations(self, stringbuilder):
		if self.optional:
			if self.literal:
				return Template('if (this.$field ) $stringbuilder.append( $value ).append(" ")').substitute(field = self.field, stringbuilder=stringbuilder, value=self.value)
			else:
				return Template('if (this.$field !=null ) $stringbuilder.append( this.$field ).append(" ")').substitute(field = self.field, stringbuilder=stringbuilder)
		else:
			if self.literal:
				return Template('$stringbuilder.append( $value ).append(" ")').substitute(stringbuilder=stringbuilder, value=self.value)
			else:
				return Template('$stringbuilder.append( this.$field ).append(" ")').substitute(field = self.field, stringbuilder=stringbuilder)
	
	def formalArgument(self):
		return self.javaType+" "+self.field
	def fieldDeclaration(self):
		return self.formalArgument()
	
	def fieldAssignement(self):
		return "this."+self.field+" = "+self.field
	
	
def unify(names):
	"change any identical names into name1, name2 etc."
	counter={}
	names= list(names)
	for i in range(len(names) ):
		name= names[i]
		if name in counter.keys():
			counter[name]+=1
			names[i]= name+str(counter[name])
		elif names.count(name)>1:
			counter[name]=1
			names[i] = name+"1"
	return names

class CompositeType(LangType):
	"a class for composite rules"
	def __init__(self, name, objects):
		super(CompositeType, self).__init__(name, objects)
	
	def parseRule(self):
		"parses the rule, and extract operation rules"
		self.rules = [ OperationRule(rule.strip()) for rule in self[0].split(' ') ]
		names = unify( rule.field for rule in self.rules if rule.field)
		for rule, name in zip((rule for rule in self.rules if rule.field), names) :
			rule.field= name
	
	def toJava(self):
		
		package = self.package
		name = self.name
		docRule= self.docRule()
		
		self.imports += self.interfaces[:] # copy interfaces always
		
		if len(self.interfaces)>0:
			implements="implements "+",".join(self.interfaces)
		else: implements = ""
		
		stringbuilder="stringbuilder"
		
		arguments = [ rule for rule in self.rules if rule.optional or not rule.literal]
		requiredArguments = [ rule for rule in self.rules if not rule.optional and not rule.literal]
		
		formalArguments=", ".join(  rule.formalArgument() for rule in arguments )
		fieldAssignements="; ".join( rule.fieldAssignement() for rule in arguments)
		
		
		constructors = Template("""public $name( $formalArguments ){
	$fieldAssignements ;
	}""").substitute(locals())
		if len(requiredArguments) != len(arguments) : # there is room for a simplified constructor
			formalArguments=", ".join(   rule.formalArgument() for rule in requiredArguments  )
			fieldAssignements="; ".join( rule.fieldAssignement() for rule in requiredArguments)
		
			constructors += Template("""public $name( $formalArguments ){
$fieldAssignements ;
}""").substitute(locals())
		
		
		
		fieldDeclaration=";\n".join( [ rule.fieldDeclaration() for rule in self.rules if rule.optional or not rule.literal] )
		fieldAssignements="; ".join( [ rule.fieldAssignement() for rule in self.rules if rule.optional or not rule.literal] )
		operations=";\n".join( [ rule.operations(stringbuilder) for rule in self.rules] )
		self.imports+=[ rule.javaType for rule in self.rules if not rule.literal]
		
		imports = self.importStatement()
		return Template( """package $package ;
$imports ;

/**
* $docRule
*/
public class $name  $implements {

$fieldDeclaration ;

$constructors


public String toString(){
	StringBuilder $stringbuilder = new StringBuilder();
	$operations ;
	return $stringbuilder.toString();
}

}""").substitute(locals() )
	
class InterfaceType(LangType):
	"a class for interface rules"
	
	def __init__(self, name, objects):
		super(InterfaceType, self).__init__( name, objects)
	
	def toJava(self):
		self.imports+=self.interfaces[:]
		if len(self.interfaces)>0:
			extends="extends "+",".join(self.interfaces)
		else: extends = ""
		
		imports = self.importStatement()
		package=self.package
		name=self.name
		docRule= self.docRule()
		return Template( """package $package ;

$imports ;

/**
* $docRule
*/
public interface $name  $extends {}""").substitute(locals() )

class ChoiceType(LangType):
	"a class for oneOf rules"
	def __init__(self, name, objects):
		super(ChoiceType, self).__init__( name, objects)
	
	def toJava(self):
		
		self.imports+=self.interfaces[:]
		
		if len(self.interfaces)>0:
			implements="implements "+",".join(self.interfaces)
		else: implements = ""
		
		constTemplate=Template('public static final $name $upper = new $name( "$raw" );');
		typeTemplate=Template('public  $name( $raw value){this( (Object)value) ;}');
		inits=[]
		
		for value in  self[0].split(' '):
			if not value or value=="": continue
			if value.lower() == value:
				inits.append( constTemplate.substitute(upper = nameSymbol(value).upper(), name= self.name, raw=value ) )
			else:
				self.imports.append( value )
				inits.append( typeTemplate.substitute(name= self.name, raw=value ) )
		inits = "\n".join(inits)
		
		imports = self.importStatement()
		package=self.package
		name=self.name
		docRule= self.docRule()
		return Template("""package $package ;
$imports ;

/**
* $docRule
*/
public class $name $implements {
	$inits
	
	Object value;
	protected $name(Object value){this.value = value;}
	public String toString(){ return String.valueOf(value);}
	
}
		""").substitute(locals() )



class ListType(LangType):
	"a class for list of stuff"
	def __init__(self,name, objects, tuples, refType):
		super(ListType, self).__init__( name, objects)
		self.separators = tuples
		self.refType = refType 
	
	def toJava(self):
		self.imports += self.interfaces[:] # import all interfaces
		
		if len(self.interfaces)>0:
			implements="implements "+",".join(self.interfaces)
		else: implements = ""
		
		component=self.refType
		self.imports+=[self.refType]
		imports = self.importStatement()
		package=self.package
		name=self.name
		
		sep,start, end =self.separators
		return Template("""package $package ;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

$imports ;

import net.ericaro.neojavagen.StringUtils;
		
/**
* <pre> $name : $start $component $sep $component $sep ... $end 
*/
public class $name extends ArrayList<$component> $implements {
	
	public $name(){
		super();
	}
	
	public $name($component ... components){
		this(Arrays.asList(components) );
	}
	public $name(Collection<$component> values ){
		super(values);
	}
	
	
	public String toString(){
		return StringUtils.toString(this, "$sep", "$start", "$end");
	}

}""").substitute(locals() )
	

def package( raw_name ):
	"format the package name"
	return projectname+"."+raw_name.lower()

def typeObject( raw_name, objects ):
	"instanciate a new type object"
	name, kind = raw_name.split(':')
	if not kind:
		return CompositeType(name, objects)
	elif kind.startswith('l'):
		tuples, refType = kind[1:].rsplit(' ')
		if not tuples:
			tuples=(' ', '', '') #default value
		else:
			tuples=eval(tuples)
		return ListType(name, objects, tuples, refType)
	elif kind.startswith('i'):
		return InterfaceType(name, objects)
	else: 
		return ChoiceType(name, objects) 
	raise Exception("unexpected kind value: "+kind )

def prod( raw_prod ):
	"instanciate a production rule"
	return raw_prod.strip()


def parse( ):
	"parses grammar.txt file"
	objects={}
	packages=[]
	current_package=None
	linen=0
	with open("Grammar.txt") as f:
		for line in f:
			linen+=1
			try:
				if line.strip() == "":
					continue
				if line.startswith(" "*16 ):
					current_prod = prod (line[16:-1] )
					current_object+=[ current_prod ]
					
				elif line.startswith(" "*8 ):
					current_object = typeObject( line[8:-1].strip() , objects)
					current_object.package = current_package
					objects[current_object.name] = current_object
				else:
					current_package = package ( line[:-1].strip()[:-1] )
					packages+=[current_package]
			except :
				print "Error line ", linen
				raise
	
	return objects, packages
			

def parseInterfaces(objects):
	"set the due interface to every objects"
	for i in objects.values():
		if isinstance ( i, InterfaceType):
			for t in i:
				try:
					objects[t].interfaces.append(i.name)
				except Exception, e:
					print "failed to set interface for ",i.name, "rule ", t, e
					

def parseRules(objects):
	"parse rules in composite "
	for i in objects.values():
		if isinstance ( i, CompositeType):
			try:
				i.parseRule()
			except Exception, e:
				print "failed to parse rules for ",i.name, "rule ", i, e



def generate(path, objects):
	"file generator"
	for i in objects.values():
		if type(i) in (InterfaceType, ChoiceType, ListType, CompositeType):
			target = path+ i.package.replace('.', '/')+"/"
			try:
				os.makedirs(target)
			except:
				pass
			with open( target+i.name+".java", 'w') as f:
				print >>f, i.toJava()

def main():
	objects,packages = parse()
	parseInterfaces(objects)
	parseRules(objects)
	
	try:
		path = sys.argv[1] 
	except:
		path = "../../../target/generated-sources/bootstrap/"
	generate(path, objects)
	
	#print objects["InterfaceModifier"].toJava()
	
	print "objects: ", len(objects) 
	print "packages: ", len(packages)
if __name__ == '__main__':
	main()

